package com.demo.controller;

import com.alibaba.fastjson.JSON;
import com.demo.dao.repository.OrderRepository;
import com.demo.model.Order;
import com.demo.model.OrderDetail;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchScrollHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * ES控制层
 * @author K
 */
@Slf4j
@RestController
public class EsOrderController {

    @Resource
    private OrderRepository orderRepository;

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 保存订单号
     * @param orderSn 订单号
     */
    @RequestMapping(value = "/saveOrder/{orderSn}",method = RequestMethod.GET)
    public void saveOrder(@PathVariable("orderSn") String orderSn) {
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setOrderId(System.currentTimeMillis());
        order.setDateOfPurchase(new Date());
        order.setAddress(orderSn + " - 地址");
        order.setPrice(BigDecimal.ONE);

        orderRepository.save(order);
    }

    /**
     * 保存订单和订单明细在同一个doc内，明细是嵌套对象
     * @param orderSn 订单号
     */
    @RequestMapping(value = "/saveOrderAndDetail/{orderSn}",method = RequestMethod.GET)
    public void saveOrderAndDetail(@PathVariable("orderSn") String orderSn) {
        long orderId = System.currentTimeMillis();
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setOrderId(orderId);
        order.setDateOfPurchase(new Date());
        order.setAddress(orderSn + " - 地址");
        order.setPrice(BigDecimal.ONE);

        List<OrderDetail> orderDetails = Stream.of(1, 2).map(s ->
            new OrderDetail().setOrderId(orderId).setName("name" + s + orderSn).setSku(s + "_" + orderSn)
        ).collect(Collectors.toList());
        order.setDetail(orderDetails);

        orderRepository.save(order);
    }

    /**
     * 通过明细查找订单，sku是嵌套对象内的字段
     * 订单和明细在同一个doc内，明细是嵌套对象。
     * @param skus sku集合
     * @return 订单集合
     */
    @PostMapping("/queryByOrderDetailSku")
    public List<Order> queryByOrderDetailSku(@RequestBody List<String> skus) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termsQuery("detail.sku", skus));
        Query query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
        SearchHits<Order> search = elasticsearchRestTemplate.search(query, Order.class);
        return search.get().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * 通过sku和name查找订单
     * @param sku 商品编码
     * @param name 商品名
     * @return 订单集合
     */
    @PostMapping("/queryByOrderDetailSkuAndName")
    public List<Order> queryByOrderDetailSku(@RequestParam("sku") String sku, @RequestParam("name") String name) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termsQuery("detail.sku", sku));
        boolQueryBuilder.must(QueryBuilders.termsQuery("detail.name", name));
        Query query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
        SearchHits<Order> search = elasticsearchRestTemplate.search(query, Order.class);
        return search.get().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * 根据订单号查询订单信息
     * @param orderSn 订单号
     * @return 订单列表
     */
    @RequestMapping(value = "/queryOrder/{orderSn}",method = RequestMethod.GET)
    public List<Order> queryOrder(@PathVariable("orderSn") String orderSn) {
        return orderRepository.findByOrderSnLike(orderSn);
    }

    /**
     * 根据订单号更新订单信息
     * @param orderSn 订单号
     * @param address 地址
     * @param price 价格
     */
    @RequestMapping(value = "/updateOrder",method = RequestMethod.POST)
    public void updateOrder(@RequestParam(name = "orderSn") String orderSn,
                            @RequestParam(name = "address") String address,
                            @RequestParam(name = "price") BigDecimal price) {
        List<Order> orders = orderRepository.findByOrderSnLike(orderSn);
        orders.forEach(s -> {
            s.setAddress(address);
            s.setPrice(price);
        });

        orderRepository.saveAll(orders);
    }

    /**
     * BoolQueryBuilder.must 相当于 and
     * @param orderSn 订单号
     * @param address 地址
     * @return 订单集合
     */
    @PostMapping(value = "/queryOrderWithCondition")
    public List<Order> queryOrderWithCondition(@ApiParam(name = "orderSn", value = "订单号") @RequestParam(name = "orderSn", required = false) String orderSn,
                                               @ApiParam(name = "address", value = "地址") @RequestParam(name = "address", required = false) String address,
                                               @ApiParam(name = "startPrice", value = "范围查询最低价格") @RequestParam(name = "startPrice", required = false) String startPrice,
                                               @ApiParam(name = "endPrice", value = "范围查询最高价格") @RequestParam(name = "endPrice", required = false) String endPrice) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (StringUtils.isNotBlank(orderSn)) {
            // 精准查询
            boolQueryBuilder.must(QueryBuilders.termQuery("orderSn", orderSn));
        }

        if (StringUtils.isNotBlank(address)) {
            // 模糊查询
            boolQueryBuilder.must(QueryBuilders.wildcardQuery("address", String.format("*%s*", address)));
        }

        if (StringUtils.isNotBlank(startPrice)) {
            // 范围查询，大于
            boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(startPrice));
        }

        if (StringUtils.isNotBlank(endPrice)) {
            // 范围查询，小于
            boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lt(endPrice));
        }

        Query query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
        SearchHits<Order> search = elasticsearchRestTemplate.search(query, Order.class);
        return search.get().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * BoolQueryBuilder.should 相当于 or
     * @param orderSn 订单号
     * @param address 地址
     * @return 订单集合
     */
    @PostMapping(value = "/queryOrderWithCondition1")
    public List<Order> queryOrderWithCondition1(@ApiParam(name = "orderSn", value = "订单号") @RequestParam(name = "orderSn", required = false) String orderSn,
                                               @ApiParam(name = "address", value = "地址") @RequestParam(name = "address", required = false) String address) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (StringUtils.isNotBlank(orderSn)) {
            // 模糊查询
            boolQueryBuilder.should(QueryBuilders.wildcardQuery("orderSn", String.format("*%s*", orderSn)));
        }

        if (StringUtils.isNotBlank(address)) {
            // 模糊查询
            boolQueryBuilder.should(QueryBuilders.wildcardQuery("address", String.format("*%s*", address)));
        }

        Query query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();

        SearchHits<Order> search = elasticsearchRestTemplate.search(query, Order.class);
        log.info("es 结果：{}", JSON.toJSONString(search));
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * es分页， from + size分页查询只能查es前1w条记录,
     * 因为ES使用index.max_result_window:10000作爲保护措施，这个参数可以动态修改，也可以在配置文件配置
     * @param pageNo 当前页码
     * @param pageSize 每页记录数
     * @return 订单集合
     */
    @PostMapping(value = "/queryOrderByPage")
    public List<Order> queryOrderByPage(@ApiParam(name = "pageNo", value = "页码") @RequestParam(name = "pageNo", required = false) int pageNo,
                                        @ApiParam(name = "pageSize", value = "每页记录数") @RequestParam(name = "pageSize", required = false) int pageSize) {

        PageRequest pageRequest = PageRequest.of(pageNo, pageSize);

        Query query = new NativeSearchQueryBuilder().withPageable(pageRequest).build();
        SearchHits<Order> searchHits = elasticsearchRestTemplate.search(query, Order.class);
        List<Order> content = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
        log.info("queryOrderByPage es 结果：{}", JSON.toJSONString(content));
        return content;
    }

    /**
     * es分页，使用scroll分页，数据量大，性能会变差，但是不受from + size分页限制。
     * 因为scroll是通过迭代查询数据
     * SpringDate ElasticSearch 4.x版本之后 pageNo不能大于0，否则报错
     * @param pageNo 当前页码
     * @param pageSize 每页记录数
     * @return 订单集合
     */
    @PostMapping(value = "/queryOrderByScroll")
    public List<Order> queryOrderByScroll(@ApiParam(name = "pageNo", value = "页码") @RequestParam(name = "pageNo", required = false) int pageNo,
                                          @ApiParam(name = "pageSize", value = "每页记录数") @RequestParam(name = "pageSize", required = false) int pageSize,
                                          @ApiParam(name = "address", value = "地址") @RequestParam(name = "address", required = false) String address) {

        PageRequest pageRequest = PageRequest.of(0, pageSize);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (StringUtils.isNotBlank(address)) {
            // 精准查询
            boolQueryBuilder.must(QueryBuilders.termQuery("address", address));
        }

        Query query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).withPageable(pageRequest).build();

        SearchScrollHits<Order> searchHits = null;
        List<Order> content = Lists.newArrayList();
        try {
            // 深度查询分页，Scroll会在es服务器上生成一个快照，保存一定的时间
            searchHits = this.elasticsearchRestTemplate.searchScrollStart(5000, query,
                    Order.class, IndexCoordinates.of("luna_order"));
            // 通过迭代方式查找指定分页数据
            for (int i = 0; i < pageNo; i++) {
                List<Order> tmp = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(tmp)) {
                    content.addAll(tmp);
                }
                //取下一页，scrollId在es服务器上可能会发生变化，需要用最新的。发起continueScroll请求会重新刷新快照保留时间
                searchHits = elasticsearchRestTemplate.searchScrollContinue(searchHits.getScrollId(), 30000, Order.class,
                                IndexCoordinates.of("luna-order"));
            }
        } finally {
            if (searchHits != null) {
                // 最后释放查询
                elasticsearchRestTemplate.searchScrollClear(Lists.newArrayList(searchHits.getScrollId()));
            }
        }
        log.info("queryOrderByScroll es 结果：{}", JSON.toJSONString(content));

        return content;
    }
}
